一文搞懂 OAuth 2.0
OAuth2.0 是一种网络授权机制,给第三方应用授权以获取用户的数据。网络授权的目的,是为API的访问授权提供一个统一开放的标准。
现实案例
你已经高中毕业很多年了,想返校看下当年的老师,看下当年自己生活学习的地方。但是你的高中是个封闭式的学校,进入是需要认证的,作为毕业生的你现在已无权进入,你只能联系你当年的老师,让老师与门卫打声招呼,授权让你进去。
授权机制的设置
- 门口增加屏幕,可以选择给你授权的人以及“获取授权”按钮,你选择老师并按下这个按钮。
- 老师的手机上弹出“有人想要获取授权”。
- 老师点击同意。
- 看到同意的消息后,门卫给了你一张卡片,上面有随机生成的授权码(令牌)以及有效期“5小时”。
- 你在屏幕上输入授权码,门自动打开,你可以进入学校了。
- 过了5小时后,你还没有出来,保安根据卡片定位找到你,并将你驱逐出去,授权码失效。
OAuth 2.0 的四种方式
一、授权码(授权码模式)
第三方应用先申请一个授权码,然后再用该码获取令牌。
这种方式是最常用的流程,安全性也最高,它适用于那些有后端的Web应用。授权码通过前端传送,令牌则是储存在后端,而且所有与资源服务器的通信都在后端完成。这样的前后端分离,可以避免令牌泄漏。
第一步,A 网站提供一个链接,用户点击后就会跳转到 B 网站。
https://b.com/oauth/authorize?
response_type=code&
client_id=CLIENT_ID&
redirect_uri=CALLBACK_URL&
scope=read
response_type
:授权类型,必选项,此处的值固定为“code”。client_id
:客户端ID,必选项。redirect_uri
:重定向URI ,可选项。scope
:权限范围,可选项。state
:客户端的当前状态,可选项。但是为了预防CSRF攻击,一定要设定此值并进行严格检查,认证服务器会原封不动地返回这个值。
第二步,跳转到 B 网站后,会要求用户登录,然后询问是否同意授权。用户同意后, B 网站就会跳回redirect_uri参数指定的网址。
https://a.com/callback?
code=AUTHORIZATION_CODE
code
:授权码,必选项。该码的有效期通常为10分钟,客户端只能使用一次。state
:如果客户端的请求中包含这个参数,则认证服务器的回应也必须包含这个参数,且值相同。
第三步,A 网站拿到授权码后,就可以向 B 网站请求令牌,请求必须是后端发起。
https://b.com/oauth/token?
client_id=CLIENT_ID&
client_secret=CLIENT_SECRET&
grant_type=authorization_code&
code=AUTHORIZATION_CODE&
redirect_uri=CALLBACK_URL
client_id
:客户端ID,必选项。client_secret
:用来让 B 确认 A 的身份,必选项。该参数是保密的,因此只能在后端发请求。grant_type
:授权模式,必选项。此处的值固定为“authorization_code”。code
:上一步获得的授权码,必选项。redirect_uri
:重定向URI,必选项。必须与第一步中的该参数值保持一致。
第四步,B 网站收到请求以后,就会颁发令牌。即向redirect_uri指定的网址,发送一段 JSON 数据。
HTTP头信息中一定要明确指明不得缓存,不得缓存是非常重要的配置项。
{
"access_token":"ACCESS_TOKEN",
"token_type":"bearer",
"expires_in":2592000,
"refresh_token":"REFRESH_TOKEN",
"scope":"read",
"uid":100101,
"info":{}
}
access_token
:访问令牌,必选项。token_type
:令牌类型,该值大小写不敏感,必选项。可以是bearer类型或mac类型。expires_in
:令牌过期时间,单位为秒,可选项。若无此参数,则必须以其它方式设置过期时间。refresh_token
:更新令牌,用来获取下一次的访问令牌,可选项。scope
:权限范围,如果与客户端申请的范围一致,此项可省略。其它参数
二、隐藏式(简化授权模式)
在纯前端的web应用场景下,就可以使用该模式。因为没有授权码这个步骤,所以称为隐藏式(隐藏了授权码)。
第一步,A 网站提供一个链接,用户点击后就会跳转到 B 网站。
https://b.com/oauth/authorize?
response_type=token&
client_id=CLIENT_ID&
redirect_uri=CALLBACK_URL&
scope=read
response_type
:授权类型,值固定为“token”,必选项。client_id
:客户端ID,必选项。redirect_uri
:重定向URI,可选项。scope
:权限范围,可选项。state
:客户端的当前状态,可选项。但是为了预防CSRF攻击,一定要设定此值并进行严格检查,认证服务器会原封不动地返回这个值。
第二步,跳转到 B 网站,会要求用户登录。用户登录同意后,B 网站就会跳回redirect_uri参数指定的网址,并且把令牌作为 URL 参数,传给 A 网站。
https://a.com/callback#
token=ACCESS_TOKEN&
token_type=TOKEN_TYPE&
expires_in=3600&
scope=read
access_token
:访问令牌,必选项。token_type
:令牌类型,该值大小写不敏感,必选项。expires_in
:令牌过期时间,单位为秒,可选项。若无此参数,则必须以其它方式设置过期时间。scope
:权限范围,如果与客户端申请的范围一致,此项可省略。state
:如果客户端的请求中包含这个参数,则认证服务器的回应也必须包含这个参数,且值相同。
需要注意的是,该模式是以锚点而不是查询字符串的方式返回令牌的,这是因为OAuth2.0允许跳转网址是 HTTP 协议,因此存在"中间人攻击"的风险。浏览器跳转时,锚点不会发到服务器,可以减少泄漏令牌的风险。
另外,把令牌直接传给前端是很不安全的。因此该模式只能用于一些对安全要求不高的场景,并且令牌的有效期必须非常短,通常只在会话期间有效,关闭浏览器,令牌就会失效。
三、密码式(密码授权模式)
前后端分离的单页应用一般会采用这种授权模式。
第一步,A 网站要求用户提供 B 网站的用户名和密码。然后 A 直接向 B 请求令牌。
https://oauth.b.com/token?
grant_type=password&
username=USERNAME&
password=PASSWORD&
scope=read
grant_type
:授权类型,此处的值固定为“password”,必选项。username
:用户名,必选项。password
:密码,必选项。scope
:权限范围,可选项。
第二步,B 网站验证身份通过后,返回令牌。
{
"access_token": "ACCESS_TOKEN",
"token_type": "bearer",
"expires_in": 3600,
"refresh_token": "REFRESH_TOKEN"
}
四、凭证式(客户端模式)
客户端以自己的名义,而不是以用户的名义,向"服务提供商"进行认证。适用于没有前端的命令行应用,即在命令行下请求令牌。
第一步,A 应用在命令行向 B 发出请求。
https://oauth.b.com/token?
grant_type=client_credentials&
client_id=CLIENT_ID&
client_secret=CLIENT_SECRET
grant_type
:授权类型,此处的值固定为“client_credentials”,表示采用凭证式。client_id
:客户端ID。client_secret
:客户端secret。scope
:权限范围。
第二步,B 网站验证通过以后,直接返回令牌。
更新令牌
B 网站颁发令牌的时候,会包含两个字段,一个是access_token,还有一个是refresh_token。令牌到期前,用户可以使用refresh_token发出一个请求,去更新令牌。
refresh_token只能刷新一次access_token, 每次刷新之后会返回一个新的access_token和access_token。
https://b.com/oauth/token?
grant_type=refresh_token&
client_id=CLIENT_ID&
client_secret=CLIENT_SECRET&
refresh_token=REFRESH_TOKEN
grant_type
:授权类型,此处的值固定为“refresh_token”,表示要求更新令牌。client_id
:客户端ID。client_secret
:客户端secret。refresh_token
:用于更新令牌的令牌。